Skip to content

feat(observability): give every cache surface one vocabulary for a hit - #10244

Merged
loopover-orb[bot] merged 1 commit into
mainfrom
feat/cache-outcome-vocabulary-10208
Jul 31, 2026
Merged

feat(observability): give every cache surface one vocabulary for a hit#10244
loopover-orb[bot] merged 1 commit into
mainfrom
feat/cache-outcome-vocabulary-10208

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

The obvious question about caching is answered wrongly by the obvious query.

Eight cache-like surfaces each report avoidance under their own event name, in three different vocabularies*_cache_hit, *_reuse, and *_one_shot_skip. No single view knows all of them. Measured on the Orb over 24h, asking %cache_hit% vs %cache_miss% for the AI review cache gives:

github_app.ai_review_cache_hit 1
github_app.ai_review_cache_miss 228
apparent hit rate 0.44%

That reads as a completely dead cache. It is not. The durable cache is bypassed by design for dynamic-context repos (the features comment in src/review/ai-review-cache-input.ts), and reuse is served by the #regate-churn cooldown, which emits different events entirely:

github_app.ai_review_one_shot_reuse 561
github_app.ai_review_frozen_reuse 249
true rate 78.1%

A 177x understatement — and exactly the kind that sends someone optimising a cache that already works.

Approach

Each of the 20 events maps to a shared (cache_surface, cache_outcome) pair, stamped into its audit metadata. Event names are untouched, so every existing dashboard, alert and query keeps working, and one query now aggregates all of them:

SELECT metadata_json->>'cache_surface', metadata_json->>'cache_outcome', count(*)
FROM audit_events WHERE metadata_json->>'cache_outcome' IS NOT NULL GROUP BY 1, 2;

Stamped centrally in recordAuditEvent, not at the ~20 call sites. A field each call site must remember to add is a field a future call site forgets — which is how three vocabularies appeared in the first place. The call sites are not touched at all; the classification happens once, where every one of them already passes through. Same reasoning as #10127 and #10200: make the omission unrepresentable rather than document it. A value a call site sets explicitly always wins, so the stamping is never lossy.

An exhaustiveness guard keeps it honest. One test scans src/** for events matching the three vocabularies and fails if any is unregistered; a second runs the reverse, failing on an event deleted from the code but left registered. A ninth surface cannot be added without being classified, and the registry cannot quietly start lying about what the aggregate covers. cacheOutcomeMetadata deliberately returns undefined for an unregistered event rather than inferring from its name — inferring would make the guard unfalsifiable.

grounding is registered but flagged not comparable

Half (b) of the issue asked whether grounding's 15.9% meant a broken key or a genuinely uncacheable surface. Measured, it is the second, and it is worse than 15.9% — it is now ~0%:

window hits misses
07-30 19:00–23:00 120 160
07-31 01:00–13:00 2 ~500

It keys on (repo, path, head_sha) and fetches the files the PR changed, whose content differs at every head SHA by construction. Its only possible hit is the same file grounded twice at the same commit — so its rate tracks re-evaluation churn, not cache health. It fell because same-SHA re-evaluation was deliberately driven down; it is falling because the system got better.

Re-keying on blob content — the obvious fix — was also measured and rejected: 1146 rows keyed by commit collapse to only 1047 keyed by content, 8.6% reuse. There is no fix to apply. So it is registered (one query still sees every surface) and carries a note so nobody reads it as a peer of the fingerprint-keyed caches or tries to optimise it.

#10204 was checked as a candidate cause and ruled out: it merged at 12:34Z, the collapse began at 01:00Z.

Closes #10208

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally; codecov/patch requires ≥99% coverage of the lines AND branches you changed
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

Detail:

  • Full suite: 26,608 passed, 0 failed.
  • Patch coverage verified line-by-line against lcov: src/services/cache-outcome.ts is 100% lines and branches (5/5, 2/2), and every changed line in recordAuditEvent is covered with all six branches taken — the two-by-two of cache/non-cache × metadata/none, plus the explicit-override case.
  • Mutation-tested both directions: unregistering one event fails the exhaustiveness guard (3 tests); removing the central stamp fails the stamping tests (3 tests).
  • Drift sweep green: db:migrations:check, db:schema-drift:check, selfhost:env-reference:check, docs:drift-check, coverage-boltons:check, dead-exports:check, dead-source-files:check, manifest:drift-check, import-specifiers:check, checkers-wired:check.
  • Unchecked boxes cover surfaces this diff does not touch (no workflow, MCP, UI, binding or schema change). npm audit reports only pre-existing advisories transitive under release-please; this PR changes no dependencies.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests.
  • API/OpenAPI/MCP behavior is updated and tested where needed.
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks.
  • Visible UI changes include a UI Evidence section below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

The added fields are two fixed enum-valued strings derived solely from the event name — no repo, PR, user, path or content data enters the metadata bag by this path, and nothing already in it is removed or overwritten.

UI Evidence

Not applicable — no visible UI, frontend, docs, or extension change.

Notes

This is half (a) of #10208, plus the measurement that resolves half (b). It deliberately does not rename any event or touch orb-collector.ts's existing AI_REVIEW_REUSE_EVENT_TYPES hand-maintained list — that list is now redundant with the registry and can be collapsed onto it, but doing so changes a live exported metric and belongs in its own change with its own before/after.

Eight cache-like surfaces each report avoidance under their own event name, in three
different vocabularies -- *_cache_hit, *_reuse and *_one_shot_skip -- and no single
view knows all of them. So the obvious question is answered wrongly by the obvious
query. Measured on the Orb over 24h: asking %cache_hit% vs %cache_miss% reports the
AI review cache at 0.44% (1 hit, 228 misses) when its real rate is 78.1%, because the
811 avoided runs live in ai_review_one_shot_reuse and ai_review_frozen_reuse, which
contain neither the word 'cache' nor the word 'hit'. A 177x understatement, and the
kind that sends someone optimising a cache that already works.

Maps each of the 20 events to a shared (cache_surface, cache_outcome) pair stamped
into its audit metadata. Event names are untouched, so every existing dashboard,
alert and query keeps working, and one query now aggregates all of them.

Stamped centrally in recordAuditEvent, NOT at the ~20 call sites: a field each call
site must remember to add is a field a future call site forgets, which is how the
vocabularies diverged in the first place. The call sites are not involved at all. A
value a call site sets explicitly always wins, so the stamping is never lossy.

An exhaustiveness guard scans src/** for events matching the three vocabularies and
fails if any is unregistered, plus the reverse direction for events deleted from the
code but left registered. A ninth surface cannot be added without classifying it.

grounding is registered but flagged not comparable: it keys on (repo, path, head_sha)
and fetches the files the PR CHANGED, whose content differs at every head SHA by
construction, so its only possible hit is the same file grounded twice at the same
commit. Its rate tracks re-evaluation churn, not cache health -- it fell from 27-61%
to ~0% because same-SHA re-evaluation was deliberately driven down. Re-keying on blob
content would collapse 1146 rows to 1047, only 8.6% reuse, so there is no fix to
apply either. Full analysis on the issue.

Closes #10208
@loopover-orb

loopover-orb Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-31 14:24:55 UTC

3 files · 1 AI reviewer · no blockers · readiness 98/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This adds a new src/services/cache-outcome.ts registry mapping ~20 known audit event types to a shared (cache_surface, cache_outcome) pair, and stamps that pair into metadata centrally in recordAuditEvent, with the caller's own metadata always taking precedence on key collision. The core approach is sound and well-tested for the events it registers, but the registry and its own build-time exhaustiveness guard are both blind to a known third ai_review reuse variant that the existing public-reuse-rate-trend.ts already tracks, so the PR's stated goal of unifying 'all three vocabularies' is incompletely delivered for that one event.

Nits — 6 non-blocking
  • src/services/cache-outcome.ts:43-45 embeds several unexplained magic numbers (27, 61, 1146, 1047, 8.6) in a comment — fine as prose but worth double-checking they stay accurate if the underlying Orb query is rerun.
  • cacheOutcomeMetadata always returns cache_outcome: 'hit' for every *_reuse/*_one_shot_skip event with no way to distinguish reuse mechanisms (frozen vs one-shot vs churn) from the aggregated metadata alone — worth confirming this granularity loss is acceptable for the dashboards this feeds.
  • The two GUARD tests in cache-outcome.test.ts rely on a regex-based source scan rather than importing the actual emitting call sites, so any event type built dynamically (e.g. via string interpolation) would silently evade both guard directions.
  • The guard test's regex only matches suffixes cache_hit/cache_miss/one_shot_skip/one_shot_reuse/frozen_reuse, so it never finds github_app.ai_review_paused_reuse — an event already referenced in src/services/public-reuse-rate-trend.ts's AI_REVIEW_REUSE_EVENT_TYPES ("frozen/paused/one-shot") — leaving that reuse event permanently unclassified with no test failure to catch it. (demoted: this PR changes test paths — whether test evidence exists is decided by the deterministic test-path classifier, not by review)
  • Add github_app.ai_review_paused_reuse to CACHE_OUTCOME_EVENTS and broaden the guard regex to also match a bare `_reuse` suffix so a future reuse-style event name can't slip past the exhaustiveness check the same way.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #10208
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 9 registered-repo PR(s), 8 merged, 299 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 9 PR(s), 299 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: moderate
Linked issue satisfaction

Addressed
The PR implements exactly the first piece of work the issue calls for: a shared (cache_surface, cache_outcome) vocabulary stamped centrally in recordAuditEvent across all the divergent event names (*_cache_hit, *_reuse, *_one_shot_skip), enabling a single aggregate query, with tests guarding exhaustiveness. It also explicitly addresses the second ask by documenting grounding as a non-comparable ou

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 9 PR(s), 299 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored self-assigned this Jul 31, 2026
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.39%. Comparing base (79d7e03) to head (00f9027).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #10244      +/-   ##
==========================================
- Coverage   92.25%   91.39%   -0.87%     
==========================================
  Files         938      939       +1     
  Lines      114694   114700       +6     
  Branches    27693    27694       +1     
==========================================
- Hits       105813   104825     -988     
- Misses       7575     8764    +1189     
+ Partials     1306     1111     -195     
Flag Coverage Δ
backend 94.14% <100.00%> (-1.55%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/db/repositories.ts 96.78% <100.00%> (+<0.01%) ⬆️
src/services/cache-outcome.ts 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit d4b477f into main Jul 31, 2026
8 checks passed
@loopover-orb
loopover-orb Bot deleted the feat/cache-outcome-vocabulary-10208 branch July 31, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

observability: cache effectiveness is unanswerable because three surfaces use three different words for a hit

1 participant